<?php

require_once "../classes/sphinx/sphinxapi.php";

/*
* Класс поиска по сайту
*
*/
class SphinxSearch {
	// Список индексов <array>
	private $_indexes;

	/*
	 * Клиент для обращени к sphinx <SphinxClient>
	 * @var SphinxClient
	 */
	private $_sph = null;

	private $_sortMode = SPH_SORT_RELEVANCE;
	private $_sortValue = '';
	
	private $_groupBy = '';
	/*
	* Конструктор
	*
	*/
	public function __construct() {
		$this->_indexes = Search_Indexes::getInstance()->getIndexes();
	}

	/*
	* Инициализация Sphinx
	*
	*/
	private function _sphinxSetup() {
		$this->_sph = new SphinxClient();
		$config = Config::getInstance();
		$this->_sph->SetServer($config->sphinx->host, (int) $config->sphinx->port);
		$this->_sph->SetConnectTimeout(1);
		$this->_sph->SetMatchMode(SPH_MATCH_EXTENDED);
		$this->_sph->SetRankingMode(SPH_RANK_WORDCOUNT);
		$this->_sph->SetArrayResult(true);
		$this->_sph->SetSortMode($this->_sortMode, $this->_sortValue);
	}

	/*
	* Поиск
	* param string $what - что ищем
	* @param string $where - где ищем строка-тип поиска (надо избавиться будет)
	* @param array $types - где ищем, массив ключей
	* @param int $pageNum - номер текущей страницы
	* @param int $pageLimit - количество отображаемых элементов
	* @param array $attributes - массив атрибутов для дополнительной фильтрации. key-value
	* @return object of stdClass	 variables: results, total, types
	*/
	public function search($what, $where, $types, $pageNum = 1, $pageLimit = 10, $attributes = array()) {
		$this->sphinxSetup();
		$indexes = (array) $types;
		$pageNum > 1 ? $start = ($pageNum - 1) * $pageLimit : $start = 0;
		$this->_sph->SetLimits($start, $pageLimit);

		foreach ($attributes as $attribute => $value) {
			if (!is_array($value)) {
				$value = array($value);
			}
			$this->_sph->setFilter($attribute, $value);
		}

		foreach ($indexes as $index) {
			$this->_sph->AddQuery($what, $index);
		}

		$result = new Search_ResultProvider($this->_sph->RunQueries(), $where);
		
		$total = array();
		$totalAll = 0;
		$content = array();
		$types = array();
		foreach ($result as $res) {
			if ($res['total'] > 0) {
				$total[$res['index']] = $res['total'] <= 1000 ? $res['total'] : 1000;
				$totalAll += $res['total'] <= 1000 ? $res['total'] : 1000;
				$types[$res['index']] = 1;
				$model_class = $res['model'];
				$model = $this->_getClassInstance($model_class);
				$rowset = $model->fetchAll('id IN (' . join(',', $res['ids']) . ')', 'FIELD(id,' . join(',', $res['ids']) . ')');
				$tmp_content = ContentList::fromRowset($rowset);
				if (!$tmp_content) {
					continue;
				}
				//$tmp_content = $this->_reorderContentList($tmp_content, $what);
				foreach ($tmp_content as $item) {
					$contentForExcerpts = array();
					$contentForExcerpts[] = strip_tags(str_replace(array('&lt;', '&gt;'), array('<', '>'), $item->getName()));
					$contentForExcerpts[] = strip_tags(str_replace(array('&lt;', '&gt;'), array('<', '>'), $item->getDescription()));
					$options = array(
//						"before_match" => "<b class='hl'>",
//						"after_match" => "</b>",
						"before_match" => "",
						"after_match" => "",
						"chunk_separator" => " ... ", // дополнить символами если длина больше limit
-						"limit" => 50 // максимальная длина имени
					);
					$highlightedContent = array();
					$highlightedContent = $this->_sph->BuildExcerpts($contentForExcerpts, $res['index'], $what, $options);
					if ($highlightedContent) {
						//$item->setName($highlightedContent[0]); - переопределяется в шаблоне
						$item->setDescription($highlightedContent[1]);
					}
				}
				$content[$res['index']]['items'] = $tmp_content;
				$content[$res['index']]['count'] = $total[$res['index']];
				$content[$res['index']]['matches'] = $res['matches'];
			}
		}
		if ('all' == $where) {
			$types = Search_Indexes::getInstance()->getStypes($types);
		} else {
			$types = $this->_searchIndexes($what);
			$types = Search_Indexes::getInstance()->getStypes($types);
		}
		$res = new stdClass();
		$res->results = $content;
		$res->total = $totalAll;
		$res->types = $types;
		$res->suggest = '';
		if (!empty($what)) {
			$suggest = SphinxSearch::suggestWords($what);
			$res->suggest = isset($suggest[0]) ? $suggest[0] : '';
		}
		return $res;
	}

		public function searchTags($what, $where, $types, $pageNum = 1, $pageLimit = 1000, $attributes = array()) {
		$this->sphinxSetup();
		$indexes = (array) $types;
		$this->_sph->SetLimits(0, $pageLimit);

		foreach ($attributes as $attribute => $value) {
			if (!is_array($value)) {
				$value = array($value);
			}
			$this->_sph->setFilter($attribute, $value);
		}

		foreach ($indexes as $index) {
			$this->_sph->AddQuery($what, $index);
		}

		$result = new Search_ResultProvider($this->_sph->RunQueries(), $where);

		$tags_array = array();
		$tagsArray = array();
		foreach ($result as $res) {
			if ($res['total'] > 0) {

				if(count($res['ids']) > 0 && count($res['ids']) <= 3000){
					$prods_ids = join($res['ids'], ',');

					$query = "SELECT DISTINCT `tag2product`.*, `tag`.`id` AS `tagId`, `tag`.`key`, `tag`.`name`
							FROM `tag2product` INNER JOIN `tag` ON `tag`.`id` = `tag2product`.`tag_id`
							WHERE (`tag2product`.`product_id` IN(".$prods_ids.")) GROUP BY `tag_id`";
					$result = DB::getConnection()->query($query);
					$tags_array[] = $result;

				}elseif(count($res['ids']) > 3000){
					$arrId = array_chunk($res['ids'], 3000); //Разбиваю массив по нужным порциям
					$i = 0;
					foreach($arrId as $ids){
						$prods_ids = join($ids, ',');
							$query ="SELECT DISTINCT `tag2product`.`tag_id`, `tag`.`id` AS `tagId`, `tag`.`key`, `tag`.`name`
									FROM `tag2product` INNER JOIN `tag` ON `tag`.`id` = `tag2product`.`tag_id`
									WHERE (`tag2product`.`product_id` IN(".$prods_ids.")) GROUP BY `tag2product`.`tag_id`";
							$result = DB::getConnection()->query($query);
							$tags_array[$i] = $result;
						$i++;
					}
				}

				$allTags = DB::getConnection()->query("SELECT `tag_id`, count(tag_id) as `col` FROM tag2product GROUP BY tag_id");
				$arrTag = array();
				$tag_max = 1;
				$arrSize = array();
				foreach($allTags as $key => $value){
					if($key == 0){
						$tag_max = $value['col'];
					}
					$arrSize[$value['tag_id']] = ceil(10 + 16 * ( $value['col'] / $tag_max));
				}

				foreach($tags_array as $tags)       //преобразую полученные данные в удобный мне массив
				{
					foreach($tags as $tag){
					#Собираем инфо о теге
					$data = array(
						'id' =>$tag['tag_id'],
						'key' => $tag['key'],
						'name' => $tag['name'],
						'font'=>$arrSize[$tag['tag_id']]
					);
					#Собираем теги в одном массиве
					$tagsArray[] = $data;
					}
				}
			}
		}
			return $tagsArray;
	}

	private $_classCache = array();

	/*
	* @param string $name
	* @return DB_Peer
	*/
	private function _getClassInstance($name) {
		if (!isset($this->_classCache[$name])) $this->_classCache[$name] = call_user_func(array($name, 'getInstance'));
		return $this->_classCache[$name];
	}

	
	public function setFilter($attribute, $values) {
		$this->sphinxSetup();
		$this->_sph->SetFilter($attribute, $values);
	}
	
	public function SetFilterRange($attribute, $min, $max, $exclude=false) {
		$this->sphinxSetup();
		$this->_sph->SetFilterRange($attribute, $min, $max, $exclude);
	}
	
	public function SetFilterFloatRange($attribute, $min, $max, $exclude=false) {
		$this->sphinxSetup();
		$this->_sph->SetFilterFloatRange($attribute, $min, $max, $exclude);
	}
	
	public function SetSelect($select) {
		$this->sphinxSetup();
		$this->_sph->SetSelect($select);
	}
	
	
	public function sphinxSetup() {
		if ($this->_sph == null) {
			$this->_sphinxSetup();
		}
	}
	
	/*
	* Метод получения всех индексов где есть искомая строка
	*
	* @param string $what - что ищем
	* @return array
	*/
	private function _searchIndexes($what) {
		$indexes = $this->getIndexes('all');
		$this->_sph->SetLimits(0, 1);
		foreach ($indexes as $index) {
			$this->_sph->AddQuery($what, $index);
		}
		$result = new Search_ResultProvider($this->_sph->RunQueries(), 'all');
		$types = array();
		foreach ($result as $res) {
			$types[$res['index']] = 1;
		}
		return $types;
	}

	/*
	* Ищем ли мы в одинарном источнике данных или группе (music:lyrics, clip,album,...etc)
	*
	* @param string $where
	* @return bool
	*/
	public function isSingleSource($where) {
		return count($this->getIndexes($where)) == 1;
	}

	/*
	* Пока sphinx 0.9.9 не умеет выдавать большую релевантность полям с полным совпадением
	* делаем это вручную над ContentList'ом
	* 
	* Выбираем ContentListElement с полным совпадением с массив и 
	* ставим их в самое начало, после возвращаем новый ContentList
	*
	* @param ContentList $cl
	* @param string $query
	* @return ContentList
	*/
	private function _reorderContentList($cl, $query) {
		mb_internal_encoding('UTF-8');
		$query = mb_strtolower($query);

		$items = array();
		$match = array();

		foreach ($cl as $i => $cli) {
			if (mb_strtolower($cli->getName()) == $query) {
				$match[] = $cli;
			} else {
				$items[] = $cli;
			}
		}

		$newList = new ContentList();
		foreach ($match as $matchItem) {
			$newList->addElement($matchItem);
		}
		foreach ($items as $item) {
			$newList->addElement($item);
		}
		return $newList;
	}

	/*
	* Получения списка поисковых индексов в зависимости от запроса
	*
	* @param string $where - где ищем
	* @return array
	*/
	public function getIndexes($where) {
		$tmpArray = array_flip($this->_indexes);
		if ('all' == $where || !in_array($where, $this->_indexes)) {
			return $this->_indexes;
		}
		return array($this->_indexes[$tmpArray[$where]]);
	}

	public function setSortMode($sortMode, $sortValue = '') {
		$this->_sortMode = $sortMode;
		$this->_sortValue = $sortValue;
	}
	
	public function setGroupBy($groupBy) {
		$this->_groupBy = $groupBy;
	}
	
	public function setGroupByNative($attribute, $func, $groupsort="@group desc") {
		$this->sphinxSetup();
		$this->_sph->SetGroupBy($attribute, $func, $groupsort="@group desc");
	}
	
	/*
	* Подсчет суммы найденых результатов по группам.
	* 
	*/
	static public function getGroupTotals(&$results) {
		$si = Search_Indexes::getInstance();
		$totals = array(
			'music' => 0,
			'film' => 0,
			'video' => 0,
			'games' => 0,
			'community' => 0
		);
		foreach ($results as $k => $item) {
			$indexNumber = $si->getIndexNumber($k);

			switch ($k) {
				case 'singer':
				case 'music':
				case 'content_music_album':
				case 'clip':
				case 'lyrics':
					$totals['music'] += $item['count'];
					break;
				case 'films':
				case 'actors':
					$totals['film'] += $item['count'];
					break;
				case 'realvideo':
					$totals['video'] += $item['count'];
					break;
				case 'mobile_games':
				case 'download_games':
				case 'online_games':
					$totals['games'] += $item['count'];
					break;
				case 'profile':
				case 'video':
				case 'blogs':
				case 'community':
				case 'starhouse':
				case 'photo':
					$totals['community'] += $item['count'];
					break;
			}
		}
		return $totals;
	}

	/*
	* Подсчет кол-ва элементов для определенных индексов
	* @param string $arg1, $arg2, ...
	*/
	public function getSubTotals($what, $indexes) {
		$res = array();
		foreach ($indexes as $index) {
			list(, $res[$index],) = $this->search($what, $index, 1, 1);
		}
		return $res;
	}

	/**
	 * Поиск максимального кол-ва элементов среди группы в результатах - для пейджинга
	 *
	 * @return int
	 */
	static public function getMaxCount(&$results) {
		$max = 0;
		foreach ($results as $item)
			if ($item['count'] > $max)
				$max = $item['count'];

		return $max;

	}

	/*
	* Ajax suggest entities names
	*	1. Название исполнителей
	*	2. Названия треков
	*	3. Названия альбомов
	*	4. Названия клипов
	*	5. Название передач
	*	6. Названия игр
	*	7. Названия сообществ
	*	8. Названия блогов
	*	9. Названия мобильных видео
	*
	* @param string $sPattern
	* @param string $sType тип поиска
	* @return array
	*/
	public static function suggestWords($sPattern, $sType = '') {
		$sPattern = str_replace(array('%', '_'), array('\%', '\_'), $sPattern);
		$sPattern = str_ireplace(array('<', '>'), '', $sPattern);
		//$sPattern = mb_strtolower($sPattern);	// utf8 /cp1251 something...

		$suggest = array();
		$query = "SELECT suggest from `search_suggest` WHERE suggest LIKE '$sPattern%' LIMIT 10";
		$results = Db::getConnection()->fetchAll($query);
		foreach ($results as $result) {
			$suggest[] = $result['suggest'];
		}

		// убираем дубликаты
		$results = array_slice(array_unique($suggest), 0, 10);
		//Cache::puts('search.suggest.'.$sPattern, $results, '(document=0)(content=0)');

		return $results;
	}

}
